package org.springframework.factory;

import org.dom4j.io.SAXReader;
import org.springframework.annotation.*;
import org.springframework.parser.SpringConfigParser;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class ClassPathXmlApplicationContext {
    //applicationContext.xml
    private String springConfig;

    //存储扫描到的Class的全路径
    private List<String> classPaths=new ArrayList<>();
    //存储接口和他对应的实现类
    private Map<Class<?>,List<Object>> iocInferfaces=new ConcurrentHashMap<>();
    //存储切面类
    private List<Class<?>> aspectClasses=new ArrayList<>();
    //IOC容器(ByType) key:扫描的class, value:这个class对应的实现类
    private Map<Class<?>,Object> iocTypeContainer=new ConcurrentHashMap<>();
    //IOC容器(ByName) key:BeanName value:对应的实现类
    private Map<String,Object> iocNameContainer=new ConcurrentHashMap<>();


    public ClassPathXmlApplicationContext(String springConfig){
        this.springConfig=springConfig;
        //初始化SpringIoc容器
        init();

    }

    //todo:循环依赖
    //初始化Ioc容器
    private void init(){
        //1.获取扫描包的根路径
        String componentScanPackage = SpringConfigParser.parseComponentScanPackage(springConfig);
        System.out.println("扫描包根路径为:"+componentScanPackage);
        //2.扫描扫描包下的类
        loadClasses(componentScanPackage);
        //3.反射获取类的实例
        createInstance();
        System.out.println("实现了接口的类的集合"+iocInferfaces);
        System.out.println("IocType对象容器集合"+iocTypeContainer);
        System.out.println("IocName对象容器集合"+iocNameContainer);
        System.out.println("切面类"+aspectClasses);
        //4.实现IOC的依赖注入
        doDI();
        System.out.println("--------------------依赖注入结束-------------------");
    }

    //加载扫描包下的所有类
    private void loadClasses(String componentScanPackage){
        URL url = Thread.currentThread().getContextClassLoader().getResource("");
        componentScanPackage = componentScanPackage.replace(".", "/");
        String classPath=url.toString().replace("file:/","");
        if (classPath.contains("test-classes")){
            classPath=classPath.replace("test-classes","classes");
        }
        classPath=classPath+componentScanPackage;
        System.out.println("资源包url为:"+classPath);
        findAllClasses(new File(classPath));
    }

    //递归扫描指定路径下的文件，获取.class文件
    private void findAllClasses(File file){
        File[] files=file.listFiles();
        for (File f:files){
            if (!f.isDirectory()){
                //不是文件夹
                String fileName = f.getName();
                if (fileName.endsWith(".class")){
                    //获取文件路径
                    String path = f.getPath();
                    String classPath = parseClassPath(path);
                    System.out.println("---扫描到类:"+classPath);
                    classPaths.add(classPath);
                }
            }else {
                //为文件夹，递归
                findAllClasses(f);
            }
        }
    }

    //将.class文件的路径转化为类的全路径 xx.xx.Book.class
    private String parseClassPath(String classPath){
        int i = classPath.indexOf("classes\\");
        classPath = classPath.substring(i + 8,classPath.length()-6);
        classPath=classPath.replace("\\",".");
        return classPath;
    }

    /**
     * 反射获取扫描包下类的实例
     *  1.调用无参方法反射生成实例
     *  2.记录实现的接口
     *  3.把扫描到的对象实例保存到ioc容器
     */
    private void createInstance(){
        try{
            for (String classPath : classPaths) {
                Class<?> c = Class.forName(classPath);
                if (c.isAnnotationPresent(Aspect.class)){
                    aspectClasses.add(c);
                }
                if(c.isAnnotationPresent(Component.class)||c.isAnnotationPresent(Service.class)|c.isAnnotationPresent(Controller.class)){
                    //调用无参方法反射生成实例
                    Object o = c.newInstance();
                    //反射获取该类实现的接口
                    Class<?>[] interfaces = c.getInterfaces();
                    if (interfaces!=null&&interfaces.length>0){
                        //有实现接口，遍历接口进行记录
                        for (Class<?> anInterface : interfaces) {
                            List<Object> objects = iocInferfaces.get(anInterface);
                            if (objects==null||objects.size()==0){
                                //之前没有这个接口的实现类
                                List<Object> objs=new ArrayList<>();
                                objs.add(o);
                                iocInferfaces.put(anInterface,objs);
                            }else {
                                //之前有这个接口的实现类，添加这个实例对象进去
                                objects.add(o);
                            }
                        }
                    }
                    System.out.println(o+"---实例化完成");
                    //把扫描到的对象实例保存到ioc容器
                    //1.放到typeCOntainer
                    iocTypeContainer.put(c,o);

                    //2.放到NameContainer
                    Component component = c.getAnnotation(Component.class);
                    Controller controller = c.getAnnotation(Controller.class);
                    Service service = c.getAnnotation(Service.class);
                    String value="";
                    if (component!=null){
                        value=component.value();
                    }else if (controller!=null){
                        value=controller.value();
                    }else if(service!=null){
                        value=service.value();
                    }
                    String beanName="";
                    if ("".equals(value)){
                        //没有指定Bean名称,BeanName默认为类名首字母小写
                        String className = c.getSimpleName();
                        beanName = String.valueOf(className.charAt(0)).toLowerCase() + className.substring(1);
                    }else {
                        //有指定Bean名称
                        beanName=value;
                    }
                    //判断是否存在同名的Bean
                    if (iocNameContainer.containsKey(beanName)){
                        throw new RuntimeException("容器中存在同名的bean,BeanName="+beanName+"---"+iocNameContainer.get(beanName)+","+o);
                    }else{
                        iocNameContainer.put(beanName,o);
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //实现IOC依赖注入(DI)
    private void doDI(){
        Set<Class<?>> classes = iocTypeContainer.keySet();
        for (Class<?> aClass : classes) {
            Object o = iocTypeContainer.get(aClass);
            Field[] declaredFields = aClass.getDeclaredFields();
            if (declaredFields!=null&&declaredFields.length>0){
                for (Field declaredField : declaredFields) {
                    boolean isAutowired = declaredField.isAnnotationPresent(Autowired.class);
                    boolean isQualifier = declaredField.isAnnotationPresent(Qualifier.class);
                    Object bean=null;
                    if (isAutowired){
                        //字段上有@Autowired
                        if (isQualifier){
                            //字段上有@Qualifier,根据BeanName注入
                            Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                            String beanName = qualifier.value();
                            bean = getBean(beanName);
                            if (bean==null){
                                throw new RuntimeException("容器中没有名称为"+beanName+"的Bean");
                            }
                        }else{
                            if(declaredField.getType().isInterface()){
                                //为接口类型，到存储接口中找
                                List<Object> beanList = getBeanByInterface(declaredField.getType());
                                if (beanList==null||beanList.size()==0){
                                    throw new RuntimeException("没有找到实现"+declaredField.getType()+"接口的bean:");
                                }else {
                                    if (beanList.size()>1){
                                        //有多个实现该接口的实例Bean，根据字段名称为beanName到iocName容器中查找
                                        String fieldName = declaredField.getName();
                                        String beanName=String.valueOf(fieldName.charAt(0)).toLowerCase()+fieldName.substring(1);
                                        bean = getBean(beanName);
                                        if (bean==null){
                                            throw new RuntimeException("有多个实现"+declaredField.getType()+"接口实例Bean");
                                        }
                                    }else {
                                        //只有一个实现该接口的实例Bean
                                        bean=beanList.get(0);
                                    }
                                }
                            }else{
                                //不是接口类型,根据类型到iocType容器中找
                                bean = getBean(declaredField.getType());
                                if(bean==null){
                                    throw new RuntimeException("没有找到类型为"+declaredField.getType()+"的bean");
                                }
                            }
                            try {
                                //反射为字段赋值
                                declaredField.setAccessible(true);
                                declaredField.set(o,bean);
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            }
        }
    }

    //根据BeanName到iocName容器取对象实例
    public Object getBean(String beanName){
        Object bean = iocNameContainer.get(beanName);
        return bean;
    }

    //根据类型到iocType容器取实例对象
    public  <E> E getBean(Class<E> type){
        Object bean = iocTypeContainer.get(type);
        return (E) bean;
    }

    //根据接口到iocInterfaces中找实例对象
    public List<Object> getBeanByInterface(Class<?> interfaceType){
        List<Object> beans = iocInferfaces.get(interfaceType);
        return beans;
    }
}
